/*
 * NetClient.c
 *      Author: DePhille
 */

#include "NetClient.h"



/**
 * Tries to log in to the server with the given username and password.
 * 	If successful, a token will be stored.
 *
 * @param username	Username.
 * @param password	Password.
 */
static void PC_Net_Login(const gchar* username, const gchar* password) {
	PC_HTTPRequest* request;	// Clean-up: Yes

	// Only execute if NOT logged in:
	if (PC_Net_LoggedIn()) {
		return;
	}

	// Check input:
	if (!username) {
		purple_debug_warning("PidginCote", "PC_Net_Login: Missing 'username' parameter.\r\n");
		return;
	} else if (!password) {
		purple_debug_warning("PidginCote", "PC_Net_Login: Missing 'password' parameter.\r\n");
		return;
	}

	// Prepare request:
	request = PC_HTTPRequest_New();
	if (!request) {
		purple_debug_error("PidginCote", "PC_Net_Login: Unable to build request object.\r\n");
		return;
	}

	// Fill in details:
	request->contentType = g_strdup("application/x-www-form-urlencoded");
	request->method = PC_HTTPREQUEST_POST;
	request->uri = g_strdup(PC_NET_SERVER_REQUESTTOKEN);
	request->payload = g_strdup_printf(
			"Email=%s"
			"&Passwd=%s",
			username,
			password);
	PC_HTTPRequest_SetCallBack(request, PC_Net_LoginCB, NULL);

	// Send request:
	purple_debug_info("PidginCote", "PC_Net_Login: Logging in...\r\n");
	PC_Net_SendRequest(request);

}



/**
 * Handles the Login response from the server.
 *
 * @param	response	The HTTPResponse.
 * @param	userData	Any userData passed via PC_Net_SendRequest.
 */
static void PC_Net_LoginCB(const PC_HTTPResponse* response, gpointer userData) {
	gchar*	authPos;		// Clean-up: No
	gchar*	authEnd;		// Clean-up: No
	gchar*	token;			// Clean-up: No
	gchar*	serverTimeStr;	// Clean-up: Yes
	time_t	serverTime;		// Clean-up: No

	// Check input:
	if (!response) {
		purple_debug_error("PidginCote", "PC_Net_LoginCB: Missing 'response' parameter. Retrying in 60 seconds.\r\n");
		purple_timeout_add_seconds(60, PC_Event_Login, NULL);
		return;
	}

	// Check code:
	if (response->code != 200) {
		purple_debug_error("PidginCote", "PC_Net_LoginCB: Unable to login. HTTP %d. Retrying in 60 seconds.\r\n", response->code);
		purple_timeout_add_seconds(60, PC_Event_Login, NULL);
		return;
	}

	// Find begin of Auth string:
	authPos = g_strstr_len(response->contents, -1, "Auth=");
	if (!authPos) {
		purple_debug_error("PidginCote", "PC_Net_LoginCB: Unable to detect Auth string. Retrying in 60 seconds.\r\n");
		purple_timeout_add_seconds(60, PC_Event_Login, NULL);
		return;
	}
	authPos += 5;

	// Find end of Auth string:
	authEnd = g_strstr_len(authPos, -1, "\r\n");

	// Detach token:
	if (authEnd) {
		token = g_strndup(authPos, (authPos - authEnd));
	} else {
		token = g_strdup(authPos);
	}

	// Detach UnixStamp:
	serverTimeStr = PC_HTTPResponse_GetHeader(response, "UnixStamp");
	if (!serverTimeStr) {
		purple_debug_error("PidginCote", "PC_Net_LoginCB: Unable to detect UnixStamp. Retrying in 60 seconds.\r\n");
		purple_timeout_add_seconds(60, PC_Event_Login, NULL);
		return;
	}
	serverTime = g_ascii_strtoull(serverTimeStr, NULL, 10);

	// Make sure timeDiff is reasonable:
	pc_net_timeDiff = serverTime - time(NULL);
	if (abs(pc_net_timeDiff) > 600) {
		purple_debug_error("PidginCote", "PC_Net_LoginCB: Servertime(INT: %li, STR: %s) differs too much: %li. Retrying in 60 seconds.\r\n", serverTime, serverTimeStr, pc_net_timeDiff);
		pc_net_timeDiff = 0;
		purple_timeout_add_seconds(60, PC_Event_Login, NULL);
		return;
	}

	// Store info:
	pc_net_timeDiff = serverTime - time(NULL);
	pc_net_token = token;
	purple_debug_info("PidginCote", "PC_Net_LoginCB: Logged in.\r\n\tToken=%s\r\n\tServerTime: %li\r\n\tTimeDiff: %li\r\n", token, serverTime, pc_net_timeDiff);

	// Clean-up:
	g_free(serverTimeStr);
}



/**
 * Uploads the given Log to the server. (Must be logged in)
 *
 * @param	log	The log to be uploaded.
 */
static void PC_Net_Upload(const PC_Log* log) {
	gchar*				fileContents;	// Clean-up: ?
	PC_HTTPRequest* request;		// Clean-up: Yes

	// Only execute if logged in:
	if (!PC_Net_LoggedIn()) {
		return;
	}


	// Check input:
	if (!log) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Missing 'log' parameter.\r\n");
		return;
	} else if (!log->protocol) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Missing 'log->protocol' parameter.\r\n");
		return;
	} else if (!log->account) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Missing 'log->account' parameter.\r\n");
		return;
	} else if (!log->recipient) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Missing 'log->recipient' parameter.\r\n");
		return;
	} else if (!log->startTime) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Missing 'log->startTime' parameter.\r\n");
		return;
	} else if (!log->fileName) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Missing 'log->fileName' parameter.\r\n");
		return;
	}

	// Read the file:
	fileContents = PC_Log_Read(log);
	if (!fileContents) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Unable to get contents.\r\n");
		return;
	}

	// Prepare request:
	request = PC_HTTPRequest_New();
	if (!request) {
		purple_debug_error("PidginCote", "PC_Net_Upload: Unable to build request object.\r\n");
		return;
	}

	// Fill in details:
	request->method = PC_HTTPREQUEST_POST;
	request->uri = g_strdup(PC_NET_SERVER_UPLOAD);
	PC_HTTPRequest_addCustomHeader(request, "PCProtocol", log->protocol);
	PC_HTTPRequest_addCustomHeader(request, "PCAccount", log->account);
	PC_HTTPRequest_addCustomHeader(request, "PCRecipient", log->recipient);
	PC_HTTPRequest_addCustomHeader(request, "PCStartDate", g_strdup_printf("%lu", log->startTime));
	request->payload = fileContents;
	PC_HTTPRequest_SetCallBack(request, PC_Net_UploadCB, NULL);

	// Send request:
	// TODO: Read log entry from server and store in DB
	PC_Net_SendRequest(request);
	purple_debug_info("PidginCote", "PC_Net_Upload: Uploading log...\r\n");
}


/**
 * Handles the Upload response from the server.
 *
 * @param	response	The HTTPResponse.
 * @param	userData	Any userData passed via PC_Net_SendRequest.
 */
static void PC_Net_UploadCB(const PC_HTTPResponse* response, gpointer userData) {
	PC_Log*			log;		// Clean-up: Yes
	xmlnode*		xmlLogNode;	// Clean-up: Yes

	// Check input:
	if (!response) {
		purple_debug_error("PidginCote", "PC_Net_UploadCB: Missing 'response' parameter.\r\n");
		return;
	} else if (userData) {
		purple_debug_warning("PidginCote", "PC_Net_UploadCB: Unexpected 'userData' parameter. Memory leak?\r\n");
	}

	// Check code:
	if (response->code != 200) {
		purple_debug_error("PidginCote", "PC_Net_UploadCB: Unable to upload file. HTTP %d.\r\n", response->code);
		return;
	}

	// Read XML:
	xmlLogNode = xmlnode_from_str(response->contents, -1);
	if (!xmlLogNode) {
		purple_debug_error("PidginCote", "PC_Net_UploadCB: Unable to parse XML:\r\n%s\r\n", response->contents);
		return;
	}

	// Try to turn XML into log:
	log = PC_Log_FromXML(xmlLogNode);
	if (!log) {
		purple_debug_error("PidginCote", "PC_Net_UploadCB: Unable to transform xmlNode into PC_Log.\r\n");
		return;
	}

	// Update log entry:
	PC_DBTable_Logs_Update(log, log);
	purple_debug_info("PidginCote", "PC_Net_UploadCB: Uploaded and updated log:\r\n\tProtocol: %s\r\n\tAccount: %s\r\n\tRecipient: %s\r\n\tStartDate: %li\r\n\tRevision: %i\r\n", log->protocol, log->account, log->recipient, log->startTime, log->revision);

	// Clean-up:
	PC_Log_Free(log);
}



/**
 * Sends a HTTPRequest to the server.
 * 	Takes ownership of the request object.
 *
 * @param	request			The HTTPRequest to send.
 * @param	callBack		This function will be called if a response is received.
 * @param	callBackData	This data will be passed to the callBack function.
 */
static void PC_Net_SendRequest(PC_HTTPRequest* request) {
	gchar*			requestStr;				// Clean-up: Yes
	unsigned short	queueResult;			// Clean-up: No

	// Only accept valid requestObj:
	if (!request) {
		purple_debug_warning("PidginCote", "PC_Net_SendRequest: Invalid request passed.\r\n");
		return;
	}

	// Check if required fields are set:
	if (!request->destination) {
		purple_debug_error("PidginCote", "PC_Net_SendRequest: Destination not set.\r\n");
		return;
	} else if (!request->userAgent) {
		purple_debug_error("PidginCote", "PC_Net_SendRequest: userAgent not set.\r\n");
		return;
	}

	// Check queue:
	queueResult = PC_Net_AddToQueue(request);
	if (queueResult == PC_NET_QUEUE_WAITING) {
		// Request queued
	} else if (queueResult == PC_NET_QUEUE_PENDING) {
		// Generate request string:
		requestStr = PC_HTTPRequest_ToString(request);
		if (!requestStr) {
			purple_debug_error("PidginCote", "PC_Net_SendRequest: Unable to convert request to string.\r\n");
			return;
		}

		// Send request:
		request->urlData = purple_util_fetch_url_request(
				request->destination,				// Destination
				TRUE,								// Full(true) or Partial(false) URL given
				request->userAgent,					// User-agent
				request->useHTTP11,					// HTTP1.1 ?
				requestStr,							// Request contents
				TRUE,								// Include HTTP headers in response?
				PC_Net_SendRequestCB,				// Callback for the answer
				request);							// Data for the callback function

		// Schedule TimeOut checker:
		request->timeSent = time(NULL);

		// Clean-up:
		g_free(requestStr);
	} else {
		purple_debug_error("PidginCote", "PC_Net_SendRequest: Unable to queue request.\r\n");
	}
}



/**
 * Handles a HTTP response from the server and invokes a callback if one was given.
 * 	See Pidgin documentation.
 */
static void PC_Net_SendRequestCB(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) {
	PC_HTTPRequest*			request;	// Clean-up: Yes
	PC_HTTPResponse*		response;	// Clean-up: Yes

	// Check input:
	if (!user_data) {
		purple_debug_error("PidginCote", "PC_Net_SendRequestCB: Missing 'user_data' parameter.\r\n");
		return;
	}

	// Re-build Request:
	request = (PC_HTTPRequest*) user_data;
	if (!request) {
		purple_debug_error("PidginCote", "PC_Net_SendRequestCB: Parameter 'user_data' is not of type PC_HTTPRequest.\r\n");
		return;
	}

	// Remove element from pending queue and fill pending queue:
	pc_net_queue_pending = g_list_remove(pc_net_queue_pending, request);
	PC_Net_UnqueueRequests();


	// Check if request succeeded:
	if (!url_text) {
		purple_debug_warning("PidginCote", "PC_Net_SendRequestCB: Request failed: %s\r\n", error_message);
		PC_Net_RetryRequest(request);
		return;
	}

	// Build HTTPResponse:
	response = PC_HTTPResponse_New(url_text);
	if (!response) {
		purple_debug_error("PidginCote", "PC_Net_SendRequestCB: Unable to make HTTPResponse object!\r\n");
		PC_Net_RetryRequest(request);
		return;
	}

	// Call callback if necessary:
	if (request->callBack) {
		request->callBack(response, request->callBackData);
	}

	// Clean-up:
	PC_HTTPRequest_Delete(request);
	PC_HTTPResponse_Delete(response);
}




/**
 * Downloads a Log from the server. (Must be logged in)
 * 	Takes ownership of the given log.
 *
 * @param	log	The log to be downloaded.
 */
static void PC_Net_Download(PC_Log* log) {
	PC_HTTPRequest*		request;		// Clean-up: No
	gchar*				tmpProtocol;	// Clean-up: Yes
	gchar*				tmpAccount;		// Clean-up: Yes
	gchar*				tmpRecipient;	// Clean-up: Yes

	// Only execute if logged in:
	if (!PC_Net_LoggedIn()) {
		return;
	}

	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Net_Download: Missing 'log' parameter.\r\n");
		return;
	} else if (!log->fileName) {
		purple_debug_warning("PidginCote", "PC_Net_Download: Missing 'log->fileName' parameter.\r\n");
		return;
	} else if (!log->protocol) {
		purple_debug_warning("PidginCote", "PC_Net_Download: Missing 'log->protocol' parameter.\r\n");
		return;
	} else if (!log->account) {
		purple_debug_warning("PidginCote", "PC_Net_Download: Missing 'log->account' parameter.\r\n");
		return;
	} else if (!log->recipient) {
		purple_debug_warning("PidginCote", "PC_Net_Download: Missing 'log->recipient' parameter.\r\n");
		return;
	} else if (!log->startTime) {
		purple_debug_warning("PidginCote", "PC_Net_Download: Missing 'log->startTime' parameter.\r\n");
		return;
	}

	// Prepare request:
	request = PC_HTTPRequest_New();
	if (!request) {
		purple_debug_error("PidginCote", "PC_Net_Download: Unable to build request object.\r\n");
		return;
	}

	// Load parameters:
	tmpProtocol = g_strdup(purple_url_encode(log->protocol));
	tmpAccount = g_strdup(purple_url_encode(log->account));
	tmpRecipient = g_strdup(purple_url_encode(log->recipient));

	// Fill in details:
	request->method = PC_HTTPREQUEST_GET;
	request->uri = g_strdup_printf("%s?protocol=%s&account=%s&recipient=%s&startDate=%lu", PC_NET_SERVER_DOWNLOAD, tmpProtocol, tmpAccount, tmpRecipient, log->startTime);
	PC_HTTPRequest_SetCallBack(request, PC_Net_DownloadCB, log);

	// Clean-up:
	g_free(tmpProtocol);
	g_free(tmpAccount);
	g_free(tmpRecipient);

	// Send request:
	purple_debug_info("PidginCote", "PC_Net_Download: Downloading log...\r\n");
	PC_Net_SendRequest(request);
}


/**
 * Handles the Download response from the server.
 * 	Stores the downloaded log on the disk and in the database.
 *
 * @param	response	The HTTPResponse.
 * @param	userData	Any userData passed via PC_Net_SendRequest.
 *
 */
static void PC_Net_DownloadCB(const PC_HTTPResponse* response, gpointer userData) {
	PC_Log*			log;		// Clean-up: Yes
	gchar*			revision;	// Clean-up: Yes

	// Check input:
	if (!response) {
		purple_debug_error("PidginCote", "PC_Net_DownloadCB: Missing 'response' parameter.\r\n");
		return;
	} else if (!userData) {
		purple_debug_error("PidginCote", "PC_Net_DownloadCB: Missing 'userData' parameter.\r\n");
		return;
	}

	// Check code:
	if (response->code != 200) {
		purple_debug_error("PidginCote", "PC_Net_DownloadCB: Unable to download file. HTTP %d.\r\n", response->code);
		return;
	}

	// Try to make PC_Log:
	log = (PC_Log*) userData;
	if (!log) {
		purple_debug_error("PidginCote", "PC_Net_DownloadCB: Custom parameter is not a PC_Log.\r\n");
		return;
	}

	// Fetch revision:
	revision = PC_HTTPResponse_GetHeader(response, "PCRevision");
	if (!revision) {
		if (log->revision == 0) {
			purple_debug_warning("PidginCote", "PC_Net_DownloadCB: No Revision in server response. Assuming revision 1.\r\n");
			PC_Log_SetRevision(log, 1);
		} else {
			purple_debug_warning("PidginCote", "PC_Net_DownloadCB: No Revision in server response. Revision was left untouched.\r\n");
		}
	} else {
		PC_Log_SetRevision(log, atoi(revision));

		// Clean-up:
		g_free(revision);
	}

	// Make sure there are some contents:
	if (!response->contents || (strlen(response->contents) <= 0)) {
		purple_debug_warning("PidginCote", "PC_Net_DownloadCB: Received an empty log from server.\r\n");
		return;
	}

	// Store file on disk:
	PC_Log_Write(log, response->contents);

	// Update last modification time:
	PC_Log_LoadLastChange(log);

	// Insert or update the log?
	if (PC_DBTable_Logs_Exists(log)) {
		PC_DBTable_Logs_Update(log, log);
		purple_debug_info("PidginCote", "PC_Net_DownloadCB: Downloaded and updated log:\r\n\tProtocol: %s\r\n\tAccount: %s\r\n\tRecipient: %s\r\n\tStartDate: %li\r\n\tFilename: %s\r\n", log->protocol, log->account, log->recipient, log->startTime, log->fileName);
	} else {
		PC_DBTable_Logs_Insert(log);
		purple_debug_info("PidginCote", "PC_Net_DownloadCB: Downloaded and inserted NEW log:\r\n\tProtocol: %s\r\n\tAccount: %s\r\n\tRecipient: %s\r\n\tStartDate: %li\r\n\tFilename: %s\r\n", log->protocol, log->account, log->recipient, log->startTime, log->fileName);
	}

	// Clean-up:
	PC_Log_Free(log);
}



/**
 * Fetches a list from the server with the available logs of a given protocol/account combination.
 *
 * @param	protocol	ProtocolID of the protocol for the account.
 * @param	account		Account name.
 */
static void PC_Net_LogList(const gchar* protocol, const gchar* account) {
	PC_HTTPRequest*	request;		// Clean-up: No
	gchar*				tmpProtocol;	// Clean-up: Yes
	gchar*				tmpAccount;		// Clean-up: Yes

	// Only execute if logged in:
	if (!PC_Net_LoggedIn()) {
		return;
	}

	// Check input:
	if (!protocol) {
		purple_debug_warning("PidginCote", "PC_Net_LogList: Missing 'protocol' parameter.\r\n");
		return;
	} else if (!account) {
		purple_debug_warning("PidginCote", "PC_Net_LogList: Missing 'account' parameter.\r\n");
		return;
	}

	// Prepare request:
	request = PC_HTTPRequest_New();
	if (!request) {
		purple_debug_error("PidginCote", "PC_Net_LogList: Unable to build request object.\r\n");
		return;
	}

	// Prepare parameters
	tmpProtocol = g_strdup(purple_url_encode(protocol));
	tmpAccount = g_strdup(purple_url_encode(account));

	// Fill in details:
	request->method = PC_HTTPREQUEST_GET;
	request->uri = g_strdup_printf("%s?protocol=%s&account=%s", PC_NET_SERVER_LOGLIST, tmpProtocol, tmpAccount);
	PC_HTTPRequest_SetCallBack(request, PC_Net_LogListCB, NULL);

	// Clean-up:
	g_free(tmpProtocol);
	g_free(tmpAccount);

	// Send request:
	purple_debug_info("PidginCote", "PC_Net_LogList: Fetching LogList...\r\n");
	PC_Net_SendRequest(request);
}



/**
 * Handles a LogList response from the server.
 * 	Builds a list of PC_Log elements and passes it to PC_Sync_LogList.
 *
 * @param	response	The HTTPResponse.
 * @param	userData	Any userData passed via PC_Net_SendRequest.
 */
static void PC_Net_LogListCB(const PC_HTTPResponse* response, gpointer userData) {
	xmlnode*		xmlDoc;		// Clean-up: Yes
	xmlnode*		curNode;	// Clean-up: No
	GList*			logs;		// Clean-up: Yes
	PC_Log*			newLog;		// Clean-up: No

	// Check input:
	if (!response) {
		purple_debug_error("PidginCote", "PC_Net_LogListCB: Missing 'response' parameter.\r\n");
		return;
	}

	// Check code:
	if (response->code != 200) {
		purple_debug_error("PidginCote", "PC_Net_LogListCB: Unable to get LogList. HTTP %d.\r\n", response->code);
		return;
	}

	// Try to build XML node:
	xmlDoc = xmlnode_from_str(response->contents, -1);
	if (!xmlDoc) {
		purple_debug_error("PidginCote", "PC_Net_LogListCB: Unable to parse XML document:\r\n%s\r\n", response->contents);
		return;
	}

	// Parse children:
	curNode = xmlnode_get_child(xmlDoc, "log");
	logs = NULL;
	while (curNode) {
		// Transform into PC_Log:
		newLog = PC_Log_FromXML(curNode);
		if (!newLog) {
			purple_debug_error("PidginCote", "PC_Net_LogListCB: Unable to build PCLog from XML node:\r\n%s\r\n", xmlnode_to_str(curNode, NULL));
		} else {

			// Add to list:
			logs = g_list_prepend(logs, newLog);
		}

		// Next node:
		curNode = xmlnode_get_next_twin(curNode);
	}

	// Parse loglist:
	PC_Sync_LogList(logs);

	// TODO: Clean-up loglist!

	// Clean-up:
	xmlnode_free(xmlDoc);
	purple_debug_info("PidginCote", "PC_Net_LogListCB: LogList received.\r\n");
}



/**
 * Adds a request to either the Pending queue or the Waiting queue.
 * 	Returns the queue it was added to.
 *
 * @param	request	The request.
 * @return	The queue it was added to or PC_NET_QUEUE_FAIL if failed.
 */
static unsigned short PC_Net_AddToQueue(PC_HTTPRequest* request) {
	// Check input:
	if (!request) {
		purple_debug_warning("PidginCote", "PC_Net_AddToQueue: Missing 'request' parameter.\r\n");
		return PC_NET_QUEUE_FAIL;
	}

	// Is the Pending queue full?
	if (g_list_length(pc_net_queue_pending) >= PC_NET_MAXSIMREQUESTS) {
		// It's full, add it to the waiting queue:
		pc_net_queue_waiting = g_list_append(pc_net_queue_waiting, request);
		return PC_NET_QUEUE_WAITING;
	} else {
		// There's still room left in the pending queue:
		pc_net_queue_pending = g_list_append(pc_net_queue_pending, request);
		return PC_NET_QUEUE_PENDING;
	}
}



/**
 * Fills up the PENDING queue by popping from the WAITING queue.
 */
static void PC_Net_UnqueueRequests(void) {
	GList*			curElem;	// Clean-up: No
	PC_HTTPRequest*	request;	// Clean-up: No
	int				freeSpots;	// Clean-up: No

	// Prepare loop conditions:
	freeSpots =	PC_NET_MAXSIMREQUESTS - g_list_length(pc_net_queue_pending);
	curElem = pc_net_queue_waiting;

	// Loop until PENDING queue is full or WAITING queue is empty:
	while ((freeSpots > 0) && curElem) {
		// Cast:
		request = (PC_HTTPRequest*) curElem->data;
		if (!request) {
			purple_debug_warning("PidginCote", "PC_Net_UnqueueRequests: Invalid request object in WAITING queue.\r\n");
		} else {
			// Remove from waiting queue and send:
			pc_net_queue_waiting = g_list_remove(pc_net_queue_waiting, request);
			PC_Net_SendRequest(request);
		}

		// Next element:
		freeSpots--;
		curElem = g_list_next(curElem);
	}


}


/**
 * Loops trough the PENDING requests and checks for time-outs.
 *
 * @param	data	Custom data (should be empty).
 * @return	Whether to repeat this function each interval. (Always FALSE)
 */
static gboolean	PC_Net_CheckTimeOuts(gpointer data) {
	GList*			curItem;	// Clean-up: No
	GList*			nextItem;	// Clean-up: No
	PC_HTTPRequest*	request;	// Clean-up: No

	// Check input:
	if (data) {
		purple_debug_warning("PidginCote", "PC_Net_CheckTimeOuts: Unused parameter 'data' was set. Memory leak?\r\n");
	}

	// Loop trough PENDING queue:
	curItem = pc_net_queue_pending;
	while (curItem) {
		// Next item:
		nextItem = g_list_next(curItem);

		// Cast:
		request = (PC_HTTPRequest*) curItem->data;
		if (!request) {
			purple_debug_warning("PidginCote", "PC_Net_CheckTimeOuts: Invalid PC_HTTPRequest detected in PENDING queue.\r\n");
		} else {
			// Check if timed out:
			if ((request->timeSent + request->timeOut) < time(NULL)) {
				// Cancel Purple Request:
				if (request->urlData) {
					purple_util_fetch_url_cancel(request->urlData);
					request->urlData = NULL;
				} else {
					purple_debug_warning("PidginCote", "PC_Net_CheckTimeOuts: Found HTTPRequest which was not linked to a Purple Request.\r\n");
				}

				// Retry if possible:
				purple_debug_warning("PidginCote", "PC_Net_CheckTimeOuts: Request timed out after %i seconds.\r\n", request->timeOut);
				PC_Net_RetryRequest(request);
			}
		}

		// Next item:
		curItem = nextItem;
	}

	// Always stop looping:
	return FALSE;
}

/**
 * Retries a request or stops retrying after the maxRetries threshold.
 *
 * @param	request	The request.
 */
static void PC_Net_RetryRequest(PC_HTTPRequest* request) {
	// Check input:
	if (!request) {
		purple_debug_warning("PidginCote", "PC_Net_RetryRequest: Missing 'request' parameter.\r\n");
		return;
	}

	// Remove from pending queue:
	pc_net_queue_pending = g_list_remove(pc_net_queue_pending, request);

	// Check threshold:
	if (request->maxRetries <= request->retries) {
		// Threshold exceeded:
		purple_debug_warning("PidginCote", "PC_Net_RetryRequest: Request failed after %i retries.\r\n", request->retries);

		// Unqueue next requests:
		PC_Net_UnqueueRequests();
	} else {
		// Retry:
		request->retries++;
		purple_debug_info("PidginCote", "PC_Net_RetryRequest: Retrying request (%i/%i).\r\n", request->retries, request->maxRetries);
		PC_Net_SendRequest(request);
	}
}



/**
 * Initializes the NetClient
 */
static void PC_Net_Init() {
	purple_timeout_add_seconds(1, PC_Net_CheckTimeOuts, NULL);
}


/**
 * Specifies if the user is logged in or not.
 *
 * @return	True if logged in, false otherwise.
 */
static gboolean			PC_Net_LoggedIn(void) {
	if (pc_net_token) {
		return TRUE;
	} else {
		return FALSE;
	}
}
